package MULE.Model.Logic;

import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Timer;
import java.util.TimerTask;

import MULE.Model.Game;
import MULE.Model.Inventory;
import MULE.Model.Player;
import MULE.Model.RandomEvent;
import MULE.Model.Resource;
import MULE.Model.Store;
import MULE.Model.Panel.GamePanel;
import MULE.Model.Panel.MapPanel;

/**
 * Logic module responsible for gameplay coordination.
 * 
 * @author jstep
 */
public class GameLogic extends MulePresenter {

	private final int MIN_TURN_TIME_S = 5;
	private final int SHORT_TURN_TIME_S = 30;
	private final int FULL_TURN_TIME_S = 60;
	private Game game;
	private ArrayList<Player> players;
	private Timer turnTimer, statusTimer;
	private TimerTask endTurn, updateGUI;
	private Player currentPlayer;
	private int currentRound, remainingTime;

	/**
	 * An enumeration of round requirements (applies to calculating turn time).
	 */
	private enum RoundRequirement {
		THRESHOLD_1(4, 3), THRESHOLD_2(8, 4), THRESHOLD_3(12, 5);

		private int roundThreshold, foodRequirement;

		private RoundRequirement(int roundThreshold, int foodRequirement) {
			this.roundThreshold = roundThreshold;
			this.foodRequirement = foodRequirement;
		}
	}

	/**
	 * Creates a new game logic module.
	 * 
	 * @param view
	 *            The view associated with this presenter.
	 */
	public GameLogic(GamePanel view, Game gameObject) {
		super(view);
		this.game = gameObject;
		this.players = game.getPlayers();
		this.turnTimer = new Timer();
		this.statusTimer = new Timer();
		this.currentRound = 0;
	}

	/**
	 * Event handler.
	 * 
	 * @param e
	 *            The event which fired this method call.
	 */
	public void actionPerformed(ActionEvent e) {
		// handle input?
	}

	/**
	 * Returns the remaining time for the current turn.
	 * 
	 * @return Remaining turn time in seconds.
	 */
	public int getRemainingTurnTime() {
		return remainingTime;
	}

	/**
	 * Sets the remaining time for the current turn.
	 * 
	 * @param time
	 *            Remaining turn time in seconds.
	 */
	public void setRemainingTurnTime(int time) {
		remainingTime = time;
	}

	/**
	 * Returns the current round number.
	 * 
	 * @return Current round number.
	 */
	public int getCurrentRound() {
		return currentRound;
	}

	/**
	 * Returns the current round number.
	 * 
	 * @return Current round number.
	 */
	public void setCurrentRound(int round) {
		currentRound = round;
	}

	/**
	 * Returns the current player.
	 * 
	 * @return Current player.
	 */
	public Player getCurrentPlayer() {
		return currentPlayer;
	}

	/**
	 * Sets the current player.
	 * 
	 * @param p
	 *            Current player.
	 */
	public void setCurrentPlayer(Player p) {
		currentPlayer = p;
	}

	/**
	 * Updates the GUI with round status. Status includes round number, current
	 * player, and remaining turn time for current player.
	 */
	private void updateRoundStatusText() {
		// Display round status.
		String status = "Round " + currentRound + " of " + game.getNumRounds()
				+ ": ";
		status += currentPlayer.getName() + "'s turn (" + remainingTime
				+ " seconds remaining).";
		MuleDriver.setCaptionText(status);
	}

	/**
	 * Starts the game with number of rounds depending on the set difficulty.
	 */
	public void startGame() {
		MuleDriver.setCaptionText("Welcome to MULE!"); // DEBUG purposes
		// Open town entrance.
		MapPanel mapPanel = (MapPanel) MuleDriver.getMapLogic().getView();
		mapPanel.addTownListener(MuleDriver.getMapLogic());
		mapPanel.setResourceTileListener(MuleDriver.getEmplacementLogic());
		Store.generateResources(); // DEBUG
		// Start rounds.
		nextRound();
	}

	/**
	 * Starts a round of the game. Each player gets one turn per round.
	 */
	public void nextRound() {
		++this.currentRound;
		// Check if game is over.
		if (gameIsOver()) {
			endGame();
		} else {
			System.out.println("Starting round " + currentRound + "...");
			// Recompute turn order.
			this.players = calcTurnOrder(game.getPlayers());
			currentPlayer = null;
			// Start first turn of round.
			nextTurn();
		}
	}

	/**
	 * Determines whether or not the current round is complete.
	 * 
	 * @return True if the current round is over, false otherwise.
	 */
	public boolean roundIsComplete() {
		// Current player has not yet been initialized.
		if (currentPlayer == null) {
			return false;
		}
		// Current player is last in round.
		return (players.indexOf(currentPlayer) + 1) >= players.size();
	}

	/**
	 * Starts the next turn of a round.
	 */
	public void nextTurn() {
		// Check if round is over.
		if (roundIsComplete()) {
			nextRound();
		} else {
			// Set current player.
			currentPlayer = nextPlayer();
			// Get turn production.
			Inventory production = game.getMap().calcProduction(currentPlayer);
			System.out.println(currentPlayer.getName() + " got " + production);
			currentPlayer.getInventory().add(production);
			// Show production to player.
			String msg = currentPlayer.getName() + "'s turn.\n";
			msg += "Turn production: " + production;
			MuleDriver
					.showAlertDialog(currentPlayer.getName() + "'s Turn", msg);
			// Random event.
			RandomEvent.invokeRandomEvent(currentPlayer,
					hasLowestScore(currentPlayer), currentRound);
			MuleDriver.updateScore(currentPlayer);
			// Start turn timer and GUI update timer.
			int turnTime = calcTurnTime(currentPlayer);
			startTurnTimer(currentPlayer, turnTime);
			startGUIUpdateTimer(turnTime);
		}
	}

	/**
	 * Returns the next player in the list of players. Wraps around to front of
	 * list if the current player is last in the list.
	 * 
	 * @return The next player in the player list.
	 */
	public Player nextPlayer() {
		if (currentPlayer == null) {
			if (players.size() == 0) {
				return null;
			} else {
				return players.get(0);
			}
		} else {
			int playerIndex = players.indexOf(currentPlayer);
			if (playerIndex == (players.size() - 1)) {
				return players.get(0);
			} else {
				return players.get(playerIndex + 1);
			}
		}
	}

	/**
	 * Ends the current player's turn prematurely.
	 */
	public void endTurn() {
		// Stop timer tasks.
		endTurn.cancel();
		updateGUI.cancel();
	}

	/**
	 * Resumes the current turn.
	 */
	public void resumeTurn() {
		startTurnTimer(currentPlayer, remainingTime);
		startGUIUpdateTimer(remainingTime);
	}

	/**
	 * Starts a new turn for the given player and time.
	 * 
	 * @param player
	 * @param turnTime
	 *            Player's turn time in seconds.
	 */
	public void startTurnTimer(Player player, int turnTime) {
		// Create task to end turn.
		endTurn = new TimerTask() {
			public void run() {
				updateGUI.cancel();
				// Alert player that their turn is over.
				MuleDriver.showAlertDialog("Turn Time Expired",
						currentPlayer.getName() + "'s turn is over!");
				// Go back to map screen.
				MuleDriver.setGameView(MuleDriver.getMapLogic().getView());
				nextTurn();
			}
		};
		// Schedule end of turn.
		turnTimer.schedule(endTurn, turnTime * 1000);
	}

	/**
	 * Starts a task to update the GUI with a countdown from given time.
	 * 
	 * @param turnTime
	 *            Count down from total turn time.
	 */
	public void startGUIUpdateTimer(int turnTime) {
		// Create task to update GUI for this turn.
		remainingTime = turnTime;
		updateGUI = new TimerTask() {
			public void run() {
				remainingTime--;
				updateRoundStatusText();
			}
		};
		// Schedule GUI update at fixed interval.
		statusTimer.scheduleAtFixedRate(updateGUI, 0, 1000);
	}

	/**
	 * Returns number of seconds game should grant to given player.
	 * 
	 * @param player
	 *            Given player.
	 * @return Player's calculated turn time in seconds.
	 */
	public int calcTurnTime(Player player) {
		// Resource requirement depends on round.
		for (RoundRequirement req : EnumSet.allOf(RoundRequirement.class)) {
			if (currentRound <= req.roundThreshold) {
				// See if player has enough food.
				int foodAmount = player.getInventory().getAmount(Resource.FOOD);
				if (foodAmount < req.foodRequirement) {
					return (foodAmount == 0) ? MIN_TURN_TIME_S
							: SHORT_TURN_TIME_S;
				}
				break;
			}
		}
		return FULL_TURN_TIME_S;
	}

	/**
	 * Returns players sorted in turn order. Turn order is determined by score.
	 * 
	 * @param List
	 *            of players.
	 * @return Player list sorted in turn order.
	 */
	public ArrayList<Player> calcTurnOrder(ArrayList<Player> players) {
		// Sort players by score (greatest to least).
		Collections.sort(players, new Comparator<Player>() {
			public int compare(Player p1, Player p2) {
				return p2.getScore() - p1.getScore();
			}
		});
		return players;
	}

	/**
	 * Determines whether or not the given player has the lowest score.
	 * 
	 * @param player
	 *            The player.
	 * @return True if the player has the lowest score, false otherwise.
	 */
	private boolean hasLowestScore(Player player) {
		int pIndex = players.indexOf(player);
		if (pIndex == players.size() - 1) {
			return true;
		}
		return false;
	}

	/**
	 * Determines whether the game is over or not.
	 * 
	 * @return True if the game is over, false if it is not.
	 */
	private boolean gameIsOver() {
		return currentRound > game.getNumRounds();
	}

	/**
	 * Signals the endgame phase.
	 */
	private void endGame() {
		MuleDriver.setCaptionText("GAME OVER");
		System.out.println("GAME OVER");
		// Determine winner.
		ArrayList<Player> players = calcTurnOrder(game.getPlayers());
		Player winner = players.get(0);
		MuleDriver.setCaptionText(winner.getName() + " WON THE GAME");
		System.out.println(winner.getName() + " WON THE GAME");
	}
}
